package com.cse587.gae.service;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import javax.jdo.PersistenceManager;
import javax.jdo.Transaction;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import com.cse587.entities.StatisticsInfo;
import com.cse587.gae.dao.*;
import com.cse587.gae.dto.AccessibleCountryList;
import com.cse587.gae.dto.ClassifiedGroup;
import com.cse587.gae.dto.CountryDto;
import com.cse587.gae.dto.GroupList;
import com.cse587.gae.entities.Country;
import com.cse587.gae.entities.CountryNameToken;
import com.cse587.gae.entities.GDP;
import com.cse587.gae.entities.HDI;
import com.cse587.gae.entities.LandArea;
import com.cse587.gae.entities.Population;
import com.cse587.util.Classification;
import com.cse587.util.InputException;
import com.cse587.util.PMF;
import com.cse587.util.Pair;
import com.cse587.util.Util;
import com.google.storage.onestore.v3.OnestoreEntity.Property.Meaning;

//Spring Service Bean
@Component
public class CountryService {
	public enum FeatureEnmu {
		GDP, HDI, POPULATION, LANDAREA
	}

	private CountryDao cDao = null;
	private GdpDao gdpDao = null;
	private HdiDao hdiDao = null;
	private PopulationDao populationDao = null;
	private LandAreaDao landAreaDao = null;

	private List<Double> previosMeans;
	private Map<Double, ArrayList<String>> groups;

	public CountryService() {
		this.previosMeans = new ArrayList<Double>();
		groups = new HashMap<Double, ArrayList<String>>();
	}

	@Autowired
	public void setcDao(CountryDao cDao) {
		this.cDao = cDao;
	}

	@Autowired
	public void setGdpDao(GdpDao gdpDao) {
		this.gdpDao = gdpDao;
	}

	@Autowired
	public void setHdiDao(HdiDao hdiDao) {
		this.hdiDao = hdiDao;
	}

	@Autowired
	public void setPopulationDao(PopulationDao populationDao) {
		this.populationDao = populationDao;
	}

	@Autowired
	public void setLandAreaDao(LandAreaDao landAreaDao) {
		this.landAreaDao = landAreaDao;
	}

	public List<Country> getAll() {
		PersistenceManager pm = PMF.get().getPersistenceManager();
		Transaction tx = pm.currentTransaction();
		tx.begin();
		List<Country> countryList = cDao.getAll();
		cDao.getCountryByName("china");
		tx.commit();

		return countryList;
	}

	public void deleteAllCountry() {
		cDao.deleteAll();
	}

	public boolean deleteCountry(String isoCode) {
		boolean status = false;
		status = cDao.deleteCountryByIso3Code(isoCode);
		return status;
	}

	public Country getCountryByName(String name) throws InputException {
		List<CountryNameToken> tokenList = cDao.getTokens(name.toLowerCase().replace("_", " "));
		String iso3CodeResult = "";
		Country c = null;
		if (tokenList.size() > 0) {
			Map<String, Integer> map = new HashMap<String, Integer>();

			for (CountryNameToken countryNameToken : tokenList) {
				Iterator<String> iter = countryNameToken.getIsoCodes()
						.iterator();
				while (iter.hasNext()) {
					String iso3Code = iter.next();
					if (map.containsKey(iso3Code)) {
						map.put(iso3Code, (map.get(iso3Code) + 1));
					} else {
						map.put(iso3Code, 1);
					}
				}
			}

			int max = 0;
			// find the iso3 with most frequency
			for (Entry<String, Integer> entry : map.entrySet()) {
				if (entry.getValue() > max) {
					max = entry.getValue();
					iso3CodeResult = entry.getKey();
				}
			}
			// get country
			c = cDao.getCountryByIso3Code(iso3CodeResult);
		} else {
			throw new InputException(Util.COUNTRY_NAME_NO_FOUND);
		}

		return c;
	}

	public Country getCountryCodeAndCapital(String countryName)
			throws InputException {
		Country c = getCountryByName(countryName);
		return c;
	}

	public Country getCountryByCoordinates(Double lat, Double longtidude)
			throws InputException {
		Country country = cDao.getCountryByCoordinates(lat, longtidude);
		return country;
	}

	public List<Country> getCountryListByFeature(String featureName,
			String order) throws InputException {
		FeatureEnmu featureEnmu = FeatureEnmu.valueOf(featureName);
		List<Country> countryList = new ArrayList<Country>();
		if (order.equals("desc") && order.equals("asc")) {
			order = "asc";
		}
		switch (featureEnmu) {
		case GDP:
			List<GDP> gdpList = gdpDao.getGdpList(order);
			for (GDP gdp : gdpList) {
				countryList.add(gdp.getCountry());
			}
			break;
		case LANDAREA:
			List<LandArea> landareaList = landAreaDao.getLandAreaList(order);
			for (LandArea landarea : landareaList) {
				countryList.add(landarea.getCountry());
			}
			break;
		case POPULATION:
			List<Population> populationList = populationDao
					.getPopulationList(order);
			for (Population population : populationList) {
				countryList.add(population.getCountry());
			}
			break;
		case HDI:
			List<HDI> hdiList = hdiDao.getHDIList(order);
			for (HDI hdi : hdiList) {
				countryList.add(hdi.getCountry());
			}
			break;
		default:
			throw new InputException(Util.INVALID_INPUT_PARAM);
		}

		return countryList;
	}

	public Country displayMap(String countryName) throws InputException {
		Country country = getCountryByName(countryName);
		if (country != null) {
			return country;
		} else {
			return null;
		}

	}

	public boolean isBorder(String countryName1, String countryName2) throws InputException{
		boolean isBorder = false;
		Country country1 = getCountryByName(countryName1);
		Country country2 = getCountryByName(countryName2);
		
		if( country1.getBorders().contains(country2.getCodeISO3()) )
			isBorder = true;
		
		return isBorder;
	}
	
	public AccessibleCountryList searchByCity(String cityName, double lat, double lng)
			throws InputException {
		
		AccessibleCountryList result = new AccessibleCountryList();
		LinkedList<String> accessibleCountry = new LinkedList<String>();// loop container
		Iterator<String> iter = accessibleCountry.iterator();
		Set<String> iso3Set = new HashSet<String>();
		// get the target country
		Country centralCountry = getCountryByCoordinates(lat, lng);
		
		System.out.println("=======>"+centralCountry.getFullName());
		
		result.setCentralCountry( Util.convertCountryToDTO(centralCountry) );
		
		if (centralCountry != null) {
			iso3Set.add(centralCountry.getCodeISO3());
			// add the targetCountry's border to stake (implement by LinkedList)
			accessibleCountry.addAll(centralCountry.getBorders());
			int i = 0;
			// loop all border country to add the border's border into loop
			// container
			while (i < accessibleCountry.size()) {
				String current = accessibleCountry.get(i);
				iso3Set.add(current);
				// accessibleCountry.remove(current);
				// add the current country's border to loop container
				List<Country> currCountry = cDao.getCountryByBorder(current);
				for (Country country : currCountry) {
					if (!accessibleCountry.contains(country.getCodeISO3())) {
						accessibleCountry.addLast(country.getCodeISO3());
					}
				}
				i++;
			}
		}

		iso3Set.remove(centralCountry.getCodeISO3());
		List<Country> resutlList = cDao.getCountryListByIso3Code(iso3Set);
		Country nearestCountry = minimumDistance(resutlList, lat, lng);
		result.setNearestCountry(Util.convertCountryToDTO(nearestCountry));
		for (Country country : resutlList) {
			result.addToAccessibleCountryList(Util.convertCountryToDTO(country));
		}
		
		return result;
	}

	public Country minimumDistance(List<Country> list, double lat, double lng) {
		Country nearestCountry = new Country();
		double minimunDistance = 0.0;
		for (Country country : list) {
			double dist = Util.distFrom(lat, lng, country.getCapitalLat(), country.getCapitalLong());
			if( dist > minimunDistance ){
				minimunDistance = dist;
				nearestCountry = country;
			}
		}
		
		return nearestCountry;
	}

	public GroupList classifyByPopulation(int groupNumber) {
		Map<String, Double> map = new HashMap<String, Double>();
		List<Country> countryList = new ArrayList<Country>();
		List<Population> populationList = populationDao.getAll();
		List<Pair> collection = new ArrayList<Pair>();
		for (Population p : populationList) {
			Pair pair = new Pair( p.getCountry().getFullName(), Double.valueOf(p.getPropertyValue()) );
			collection.add(pair);
		}

		Classification classification = new Classification(groupNumber);
		classification.setCollection(collection);
		classification.init();
		classification.classify();
		
		GroupList glist = new GroupList();
		for (Map.Entry<Integer, ArrayList<Pair>> entry: classification.getClusters().entrySet()) {
			ClassifiedGroup cgroup = new ClassifiedGroup(entry.getValue(), "No." +(entry.getKey()+1) +" group");
			glist.getGroupList().add(cgroup);
		}

		return glist;
	}

}
